package com.example.mq.mqsever.core;

/**
 * Created with IntelliJ IDEA.
 * Description:
 * User: WHY
 * Date: 2023-09-05
 * Time: 11:03
 */

import com.example.mq.common.MqException;
import org.apache.tomcat.util.descriptor.web.WebXml;

import java.util.Map;

/**
 * 使用这个类实现交换机的转发规则
 * 同时也借助这个类验证bindingKey是否合法
 */
public class Router {
    public boolean checkBindingKey(String bindingKey) {
        //1.bindingKey的构造规则
        //1.数字字母下划线
        //2.使用.分割成若干部分
        //3.允许存在#和*作为通配符,但通配符只能作为独立的分段

        if (bindingKey.length() == 0) {
            //空字符串合法,在使用direct/fanout交换机的时候,就可以
            return true;
        }
        for (int i = 0; i < bindingKey.length(); i++) {
            char ch = bindingKey.charAt(i);
            if (ch >= 'A' && ch <= 'Z') {
                continue;
            }
            if (ch >= 'a' && ch <= 'z') {
                continue;
            }
            if (ch >= '0' && ch <= '9') {
                continue;
            }
            if (ch == '_' || ch == '.' || ch == '*' || ch == '#') {
                continue;
            }
            return false;
        }
        //检查*或者#是否是独立的部分
        //aaa.*.bbb合法
        //aaa.a*.bbb非法情况

      String[] words=  bindingKey.split("\\.");
        for(String word:words){
            //检查word长度是不是大于1并且包含了#或者*,就是非法的格式了
            if(word.length()>1&&(word.contains("*")||word.contains("#"))){
                return false;
            }
            //人为约定一下,通配符之间的相邻关系
            //1. aaa.#.#.bbb   非法
            //2. aaa.#.*.bbb    非法
            //3. aaa.*.#.bbb   非法
            //4. aaa.*.*.bbb    合法

            for(int i=0;i<words.length-1;i++){
                if(words[i].equals("#")&&words[i+1].equals("#")){
                    return false;
                }
                if(words[i].equals("#")&&words[i+1].equals("*")){
                    return false;
                }
                if(words[i].equals("*")&&words[i+1].equals("#")){
                    return false;
                }

            }

        }
        return true;

}

//[测试用例]
    //    bindingKey             routingKey         result
       // aaa                      aaa                 true
        //aaa.bbb                  aaa.bbb             true
        //aaa.bbb                  aaa.bbb.ccc         false
        //aaa.bbb                  aaa.ccc             false
        //aaa.bbb.ccc              aaa.bbb.ccc         true
        //aaa.*                    aaa.bbb             true
        //aaa.*.bbb                aaa.bbb.ccc         false
        //*.aaa.bbb                aaa.bbb             false
        //#                        aaa.bbb.ccc         true
        //aaa.#                    aaa.bbb             true
        //aaa.#                    aaa.bbb.ccc         true
        //aaa.#.ccc                aaa.ccc             true
        //aaa.#.ccc                aaa.bbb.ccc         true
        //aaa.#.ccc                aaa.aaa.bbb.ccc     true
        //#.ccc                    ccc                 true
        //#.ccc                    aaa.bbb.ccc         true





    //routingKey构造规则
    //1.数字字母下划线
    //2.使用.分割字符串
    public boolean checkRoutingKey(String routingKey){
        if(routingKey.length()==0){
            //空字符串合法,比如在使用fanout交换机,routingKey用不上
            return true;
        }
        for(int i=0;i<routingKey.length();i++){

            char ch=routingKey.charAt(i);
            //判定该字符是否是大写字母
            if(ch>='A'&&ch<='Z'){
                continue;
            }
            //判断该字符是否是小写字母
            if(ch>='a'&&ch<='z'){
                continue;
            }
            //判断该字母是否是阿拉伯数字
            if(ch>='0'&&ch<='9'){
                continue;
            }
            //判断该字母是否是_或者.
            if(ch=='_'||ch=='.'){
                continue;
            }
            //不符合上述任意一种情况
            return false;
        }



        return true;
    }
//这个方法用来判定该消息是否可以转发给这个绑定对应的队列(是否匹配)
    public boolean route(Exchangetype exchangetype,Binding binding,Message message) throws MqException {
       //根据不同的exchangeType使用不同的判定转发规则
        if(exchangetype==Exchangetype.FANOUT){
            //如果是FANOUT,则交换机上绑定的所有队列都需要转发
            return true;
        }else if(exchangetype==Exchangetype.TOPIC){
            return routeTopic(binding,message);


        }else{
            //其他情况不应该存在
            throw new MqException("[Router] 交换机非法! exchangeType="+exchangetype);
        }

    }

    private boolean routeTopic(Binding binding, Message message) {
       //先把两个key进行切分
       String[] bindingTokens=binding.getBindingKey().split("\\.");
       String[] routingTokens=message.getRootingKey().split("\\.");

       //引入两个下标,指向上述两个数组,初始情况都为0
        int bindingIndex=0;
        int routingIndex=0;
        //此处使用while更合适,每次循环,下标不一定是+1,不适合用for
        while(bindingIndex<bindingTokens.length && routingIndex<routingTokens.length){
            if(bindingTokens[bindingIndex].equals("*")){
                //如果遇到了*,直接进入下一轮,因为*可以匹配到任何一个部分
                bindingIndex++;
                routingIndex++;
                continue;
            }else if(bindingTokens[bindingIndex].equals("#")){
                //如果遇到#,需要看看有没有下一个位置
                bindingIndex++;
                if(bindingIndex==bindingTokens.length){
                    return true;
                }

                //#后面还有东西,拿着这个内容,去routingKey中往后找,找到对应的位置
                //findNextMatch这个方法用来查找该部分在routingKey的位置,并返回该下标,没找到返回-1
                routingIndex= findNextMatch(routingTokens,routingIndex,bindingTokens[bindingIndex]);
                if(routingIndex==-1){
                    //没找到,匹配失败
                    return false;
                }
                //找到匹配的,继续往后匹配
                bindingIndex++;
                routingIndex++;
            }else{
                //如果遇到普通字符串,就要求两边内容一样
                if((!bindingTokens[bindingIndex].equals(routingTokens[routingIndex]))){
                    return false;
                }
                bindingIndex++;
                routingIndex++;
            }
        }

        //是否是双方同时到达末尾
        //aaa.bbb  aaa.bbb.ccc就是匹配失败的
        if(bindingIndex==bindingTokens.length&&routingIndex==routingTokens.length){
            return  true;
        }
        return false;
    }

    private int findNextMatch(String[] routingTokens, int routingIndex, String bindingToken) {
        for(int i=routingIndex;i<routingTokens.length;i++){
            if(routingTokens[i].equals(bindingToken)){
                return i;
            }
        }
        return -1;
    }

}
